package anatex.kea.genex.extractor;

import java.util.*;

import javax.management.openmbean.KeyAlreadyExistsException;

import org.apache.openjpa.lib.conf.IntValue;

import anatex.kea.*;


public class Parameters extends HashMap<Integer, Object> {
	
	static final public int NUM_PHRASES 			= 1;
	static final public int FACTOR_TWO_ONE 			= 2;
	static final public int FACTOR_THREE_ONE 		= 3;
	static final public int MIN_LENGTH_LOW_RANK 	= 4;
	static final public int MIN_RANK_LOW_LENGTH 	= 5;
	static final public int FIRST_LOW_THRESH 		= 6;
	static final public int FIRST_HIGH_THRESH 		= 7;
	static final public int FIRST_LOW_FACTOR 		= 8;
	static final public int FIRST_HIGH_FACTOR 		= 9;
	static final public int STEM_LENGTH 			= 10;
	static final public int SUPPRESS_PROPER 		= 11;
	static final public int NUM_WORKING 			= 12;
	
	static final public int DOUBLE_MULTIPLIER 		= 100;
	static final public int WORKING_MULTIPLIER		= 5;
	
	private int[] parametersOrder;
	private HashMap<Integer, int[]> parametersDescriptor;
	private HashMap<Integer, String> binaryStrings;

	public Parameters() {
		super();
		initParametersProperties();
	}
	
	protected void initParametersProperties() {
		binaryStrings = new HashMap<Integer, String>();
		
		parametersOrder = new int[11];
		for (int i = NUM_PHRASES; i <= SUPPRESS_PROPER; i ++) {
			parametersOrder[i-1] = i;
		}
		
		parametersDescriptor = new ParametersDescriptor();
	}
	
	protected String zeroPad(String value, int size) {
	  String s = "000000000000"+value;
	  
	  return s.substring(s.length() - size);
	}
	
	protected String zeroUnpad(String value) {
		int offset = 0;
		
		for (int i = 0; i < value.length() - 1; i ++) {
			if ('0' != value.charAt(i)) {
				break;
			}
			
			offset = i + 1;
		}
		
		if (offset == value.length()) {
			return value;
		}
		
		return value.substring(offset);
	}
	
	public void setParameters(HashMap<Integer, Object> hMap) throws KeyAlreadyExistsException {
		for (int key : hMap.keySet()) {
			this.put(key, hMap.get(key));
		}
	}
	
	/**
	 * Set parameters from a binaryString
	 * @param String bString
	 */
	public void setParameters(String bString) throws KeyAlreadyExistsException {
		int[] desc = {};
		Object[] paramBounds = {};
		int parsedValue = 0;
		Object actualValue = null;
		
		for (int key : parametersDescriptor.keySet()) {
			desc = parametersDescriptor.get(key);
			parsedValue = Integer.parseInt(zeroUnpad(bString.substring(desc[ParametersDescriptor.INDEX_OFFSET], desc[ParametersDescriptor.INDEX_OFFSET] + desc[ParametersDescriptor.INDEX_LENGTH])), 2);
			paramBounds = ParametersDescriptor.getParamBounds(key);
			
			//keep params inide their bounds
			switch (desc[ParametersDescriptor.INDEX_TYPE]) {
				case ParametersDescriptor.TYPE_INTEGER :
					if (parsedValue < (int) ((Integer) paramBounds[0])) {
						parsedValue = (int) ((Integer) paramBounds[0]);
					}
					
					if (parsedValue > (int) ((Integer) paramBounds[1])) {
						parsedValue = (int) ((Integer) paramBounds[1]);
					}
					
					actualValue = (Object)parsedValue;
					break;
					
				case ParametersDescriptor.TYPE_FLOAT :
					if (parsedValue < ((Double) paramBounds[0]) * DOUBLE_MULTIPLIER) {
						parsedValue = new Double(((Double) paramBounds[0]) * DOUBLE_MULTIPLIER).intValue();
					}
					
					if (parsedValue > ((Double) paramBounds[1]) * DOUBLE_MULTIPLIER) {
						parsedValue = new Double(((Double) paramBounds[1]) * DOUBLE_MULTIPLIER).intValue();
					}
					
					actualValue = (Object)new Double((double)parsedValue / DOUBLE_MULTIPLIER);
					break;
			}
			
			this.put(key, actualValue);
		}
	}
	
	/**
	 * Returns the type of the parameter - one of PARAMETER_TYPE_INTEGER or PARAMETER_TYPE_FLOAT
	 * @param int key
	 * @return int
	 */
	public int getParameterType(int key) {
		return parametersDescriptor.get(key)[ParametersDescriptor.INDEX_TYPE];
	}
	
	public Object put(Integer k, Object v) throws KeyAlreadyExistsException {
		//if setting number of phrases - set number of working phrases
		if (NUM_PHRASES == k) {
			super.put(NUM_WORKING, WORKING_MULTIPLIER * (Integer)v);
		}
		
		if (NUM_WORKING == k) {
			throw new KeyAlreadyExistsException("NUM_WORKING is automatically set and can only be read.");
		}
		
		switch (k) {
			case NUM_PHRASES:
			case MIN_RANK_LOW_LENGTH:
			case FIRST_LOW_THRESH:
			case FIRST_HIGH_THRESH:
			case STEM_LENGTH:
			case SUPPRESS_PROPER:
				binaryStrings.put(k, zeroPad(Integer.toBinaryString(((Integer)v).intValue()), parametersDescriptor.get(k)[ParametersDescriptor.INDEX_LENGTH]));
				break;
				
			default :
				int i = ((Long)Math.round((Double)v * DOUBLE_MULTIPLIER)).intValue();
				binaryStrings.put(k, zeroPad(Integer.toBinaryString(i), parametersDescriptor.get(k)[ParametersDescriptor.INDEX_LENGTH]));
				break;
		}
		
		return super.put(k, v);
	}
	
	public String toBinaryString() {
		StringBuffer binaryString = new StringBuffer();
		
		for (int key : parametersOrder) {
			binaryString.append(binaryStrings.get(key).toString());
		}
		
		return binaryString.toString();
	}
	
}
